/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package SerialCom;

//~--- non-JDK imports --------------------------------------------------------
import gnu.io.*;

//~--- JDK imports ------------------------------------------------------------

import java.io.*;

import java.util.*;

/**
 *
 * @author sf
 */
public class Rs485 implements SerialPortEventListener {

    /** Field description */
    private static final byte PKT_START = 0x02;
    /** Field description */
    String defaultPort = "COM4";
    /** Field description */
    String messageString = "Hello, world!";
    /** Field description */
    boolean portFound = false;
    private byte[] cData;
    private byte cLen;
    private CommProt commProt;
    private byte command;
    /** Field description */
    public byte[] data;
    /** Field description */
    InputStream inputStream;
    /** Field description */
    public int len;
    /** Field description */
    MainClass mainClass;
    /** Field description */
    OutputStream outputStream;
    /** Field description */
    CommPortIdentifier portId;
    /** Field description */
    Enumeration portList;
    private byte rs485RxChar;
    /** Field description */
    public RxCommPack rxCommPack;
    /** Field description */
    SerialPort serialPort;
    private TxCommPack txCommPack;

    /**
     * Constructs ...
     *
     *
     * @param Address
     * @param mainClass
     */
    Rs485(char Address, MainClass mainClass) {
        rxCommPack = new RxCommPack();
        rxCommPack.c485Buf = new byte[64];
        txCommPack = new TxCommPack();
        commProt = new CommProt(Address);
        this.mainClass = mainClass;
        rs_test(this.mainClass);
    }

    public void setCommand(byte command) {
        this.command = command;
    }

    public byte getCommand() {
        return command;
    }

    public byte[] getData() {
        return data;
    }

    public int getLen() {
        return len;
    }

    public void setrs485RxChar(byte rs485RxChar) {
        this.rs485RxChar = rs485RxChar;
        System.out.println(rs485RxChar);
        rs485Decode(rs485RxChar);
        rs485Process(rs485RxChar);
    }

    RS485Status.rxState rs485Decode(byte rs485RxChar) {
        switch (commProt.cRS485State.getCurrentState()) {
            case WAIT_START:
                db(rs485RxChar);
                db(commProt.cRS485State.getCurrentState().toString());
                rxCommPack.cStart = rs485RxChar;

//          Check for the start of packet byte
                if (rxCommPack.cStart == PKT_START) {
                    db(commProt.cRS485State.getCurrentState().toString());
                    commProt.cRS485State.setNextRxState();
                }

                break;

            case WAIT_ADDR_LOW:
                db(commProt.cRS485State.getCurrentState().toString());

                char tmp = (char) (rxCommPack.cNetAddr & 0x0f);

                tmp = (char) (rs485RxChar | tmp);
                rxCommPack.cNetAddr = (char) (rs485RxChar | (rxCommPack.cNetAddr));
                commProt.cRS485State.setNextRxState();

                break;

            case WAIT_ADDR_HIGH:
                db(commProt.cRS485State.getCurrentState().toString());

                char tmp1;
                char tmp2;

                tmp1 = (char) (rs485RxChar << 8);
                tmp2 = (char) (rxCommPack.cNetAddr & 0xf0);

                char chk = (char) (tmp1 | tmp2);

                rxCommPack.cNetAddr = (char) ((rs485RxChar << 8) | (rxCommPack.cNetAddr & 0xf0));
                commProt.cRS485State.setNextRxState();

                break;

            case WAIT_LEN:
                db(commProt.cRS485State.getCurrentState().toString());
                rxCommPack.cLenExpected = rs485RxChar;

                if (rxCommPack.cLenExpected > rxCommPack.c485Buf.length) {
                    commProt.cRS485State.setCurrentState(
                            RS485Status.rxState.INVALID);

                    int a;

                    commProt.cError = RS485Status.errState.BAD_LENGTH;
                } else {
                    commProt.cBufPtr = 0;
                    commProt.cRS485State.setNextRxState();
                }

                break;

            case PKT_CMD:
                db(commProt.cRS485State.getCurrentState().toString());
                rxCommPack.cCommand = rs485RxChar;

                // FIXME PacketHasPayload
                if (true) {
                    commProt.cRS485State.setNextRxState();

                // commProt.cRS485State = rxState.WAIT_DATA;
                } else {
                    commProt.cRS485State.setCurrentState(
                            RS485Status.rxState.WAIT_CRC_HIGH);
                }

                break;

            case WAIT_DATA:
                db(commProt.cRS485State.getCurrentState().toString());
                rxCommPack.c485Buf[(int) commProt.cBufPtr] = rs485RxChar;
                commProt.cBufPtr++;

                // If last byte of data received
                if (commProt.cBufPtr == rxCommPack.cLenExpected) {

                    // next byet is the CRC high byte
                    commProt.cRS485State.setNextRxState();
                }

                break;

            case WAIT_CRC_HIGH:
                db(commProt.cRS485State.getCurrentState().toString());
                rxCommPack.cRxCrc = rs485RxChar;
                commProt.cRS485State.setCurrentState(
                        RS485Status.rxState.WAIT_CRC_LOW);

                break;

            case WAIT_CRC_LOW:
                db(commProt.cRS485State.getCurrentState().toString());
                rxCommPack.cRxCrc = rs485RxChar;
                commProt.cRS485State.setCurrentState(RS485Status.rxState.COMPLETE);

                break;

            case COMPLETE:
                db(commProt.cRS485State.getCurrentState().toString());

                break;    // Idle state

            case VALID:
                db(commProt.cRS485State.getCurrentState().toString());

                break;    // Idle state

            case INVALID:
                db(commProt.cRS485State.getCurrentState().toString());

                break;    // Idle state

            default:
                db(commProt.cRS485State.getCurrentState().toString());
                commProt.cRS485State.setCurrentState(
                        RS485Status.rxState.WAIT_START);

                break;
        }

        return commProt.cRS485State.getCurrentState();
    }

    public boolean rs485Process(byte rs485RxChar) {
        boolean cOurPkt, cPktReady;

        cOurPkt = false;
        cPktReady = false;

        if (commProt.cRS485State.getCurrentState() == RS485Status.rxState.COMPLETE) {

            /* State Machine Received the packet */
            /* (Invalid) and destined for this node */
            if (rxCommPack.cNetAddr == commProt.cOurAddr) {
                commProt.isPacketForMe = true;
                cOurPkt = true;
                db(commProt.cRS485State.toString());
            } else {
                commProt.isPacketForMe = false;
                cOurPkt = false;
            }

            /* Validate packet CRC */
            commProt.cRS485State.setCurrentState(
                    postValidatePacket(rxCommPack));

            if ((commProt.cRS485State.getCurrentState() == RS485Status.rxState.INVALID) || (commProt.cRS485State.getCurrentState() == RS485Status.rxState.VALID)) {

                // Reject invalid packets
                if (commProt.cRS485State.getCurrentState() == RS485Status.rxState.INVALID) // NAK our invalid packets
                {

                    // ClearLine2();
                    db("Invalid pck");

                    if (commProt.cError == RS485Status.errState.BAD_CRC) {

                        /* FIXME          LcdWrite("Bad CRC"); */
                    } else if (commProt.cError == RS485Status.errState.BAD_LENGTH) {

                        /* FIXME    what are we going to do if something is wrong */

                        // Nop(); //LcdWrite("Bad length");
                    }

                    // Delay_Us(2000);
                    // FIXME CHECK
                    // if ( cOurPkt ) Rs485SendPacket( SENSOR_NAK, 0, NULL );
                    commProt.cRS485State.setCurrentState(
                            RS485Status.rxState.WAIT_START);
                } else if (commProt.cRS485State.getCurrentState() == RS485Status.rxState.VALID) // If packet valid
                {    // and destined for this node
                    if (cOurPkt) {
                        db("Our Packet Valid");
                        cPktReady = true;
                        commProt.isPacketReady = true;
                    } else {
                        db("Packet Valid NOT FOR US");

                        // commProt.cRS485State = rxState.WAIT_START;
                        commProt.cRS485State.setCurrentState(
                                RS485Status.rxState.COMPLETE);
                    }
                }
            }
        }

        return cPktReady;
    }

    private RS485Status.rxState postValidatePacket(RxCommPack rxpack) {
        return RS485Status.rxState.VALID;
    }

    private RxCommPack rs485GetPacket() {
        int c;

        // byte command;
        command = rxCommPack.cCommand;
        cLen = rxCommPack.cLenExpected;

        for (c = 0; c < rxCommPack.cLenExpected; c++) {
            cData[c] = rxCommPack.c485Buf[c];
        }

        cData[rxCommPack.cLenExpected] = 0x00;    // Termninate

        return rxCommPack;
    }

    private void db(String a) {
        System.out.println(a);
    }

    private void db(RS485Status.rxState rxs) {
        System.out.println(rxs.toString());
    }

    private void db(byte rs485RxChar) {
        System.out.println(rs485RxChar);
    }

    // FIXME?????
    public boolean isPacketComplete() {
        if (commProt.cRS485State.getCurrentState() == RS485Status.rxState.COMPLETE) {
            return true;
        } else {
            return false;
        }
    }

    public boolean isPacketForMe() {
        if (commProt.isPacketForMe) {
            return true;
        } else {
            return false;
        }
    }

    public boolean isPacketReady() {
        if (commProt.isPacketReady) {
            return true;
        } else {
            return false;
        }
    }

    public boolean isErrState() {
        if (commProt.cError != RS485Status.errState.OK_CRC) {
            return true;
        } else {
            return false;
        }
    }

    void rs_test(MainClass main) {
        portList = CommPortIdentifier.getPortIdentifiers();

        while (portList.hasMoreElements()) {
            portId = (CommPortIdentifier) portList.nextElement();

            if (portId.getPortType() == CommPortIdentifier.PORT_SERIAL) {
                if (portId.getName().equals(defaultPort)) {
                    System.out.println("Found port: " + defaultPort);
                    portFound = true;

                    // initalize serial port
                    System.out.println("punto critico");

                    try {
                        serialPort = (SerialPort) portId.open("SimpleReadApp",
                                2000);
                    } catch (PortInUseException e) {
                    }

                    try {
                        inputStream = serialPort.getInputStream();
                    } catch (IOException e) {
                    }

                    try {
                        serialPort.addEventListener(this);
                    } catch (TooManyListenersException e) {
                    }

                    // activate the DATA_AVAILABLE notifier
                    serialPort.notifyOnDataAvailable(true);

                    try {

                        // set port parameters
                        serialPort.setSerialPortParams(9600,
                                SerialPort.DATABITS_8,
                                SerialPort.STOPBITS_1,
                                SerialPort.PARITY_NONE);
                    } catch (UnsupportedCommOperationException e) {
                    }
                }
            }
        }
    }

    public void initwritetoport() {

        // initwritetoport() assumes that the port has already been opened and
        // initialized by "public nulltest()"
        try {

            // get the outputstream
            outputStream = serialPort.getOutputStream();
        } catch (IOException e) {
        }

        try {

            // activate the OUTPUT_BUFFER_EMPTY notifier
            serialPort.notifyOnOutputEmpty(true);
        } catch (Exception e) {
            System.out.println("Error setting event notification");
            System.out.println(e.toString());
            System.exit(-1);
        }
    }

    public void serialEvent(SerialPortEvent event) {
        switch (event.getEventType()) {
            case SerialPortEvent.BI:
            case SerialPortEvent.OE:
            case SerialPortEvent.FE:
            case SerialPortEvent.PE:
            case SerialPortEvent.CD:
            case SerialPortEvent.CTS:
            case SerialPortEvent.DSR:
            case SerialPortEvent.RI:
            case SerialPortEvent.OUTPUT_BUFFER_EMPTY:
                break;

            case SerialPortEvent.DATA_AVAILABLE:

                // we get here if data has been received
                byte[] readBuffer = new byte[20];

                // byte[] a;
                try {

                    // read data
                    while (inputStream.available() > 0) {
                        do {
                            this.setrs485RxChar((byte) inputStream.read());
                            System.out.print(this.getCommand());
                        } while (!(this.isPacketComplete() | this.isErrState() | this.isPacketReady()));

                        if (this.isPacketForMe()) {
                            readBuffer = this.rxCommPack.c485Buf;
                        } else {
                            readBuffer = null;
                        }
                    }

                    // print data
                    String result = new String(readBuffer);

                    System.out.println("Read: " + result.trim());
                    mainClass.setStringUpdate(result.trim());
                    mainClass.setRxPackUpdate(this.rxCommPack);
                } catch (IOException e) {
                }

                break;
        }
    }

    public void writetoport() {
        System.out.println("Writing \"" + messageString + "\" to " + serialPort.getName());

        try {

            // write string to serial port
            outputStream.write(messageString.getBytes());
        } catch (IOException e) {
        }
    }
}
